package com.yifeng.repo.controller.postman.work.task.handler.impl;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.yifeng.repo.base.utils.codec.HmacSHA1;
import com.yifeng.repo.base.utils.codec.URLEncodeHelper;
import com.yifeng.repo.base.utils.common.Snowflake;
import com.yifeng.repo.base.utils.converter.DateHelper;
import com.yifeng.repo.base.utils.converter.JacksonHelper;
import com.yifeng.repo.controller.postman.work.dto.PostTaskDto;
import com.yifeng.repo.controller.postman.work.task.handler.PostInterceptor;
import org.springframework.http.MediaType;

import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Created by daibing on 2023/5/12.
 */
public class StandardPostInterceptor implements PostInterceptor {
    private static final String ACCESS_KEY_SECRET = "AccessKeySecret";

    /**
     * 注意处理业务投递参数中的占位符，变量类似 {{AccessKeyId.DATA}}，动作类似 {{Signature.ACTION}}
     */
    @Override
    public void apply(PostTaskDto postTaskDto) {
        // 1. method
        postTaskDto.setMethod(postTaskDto.getHttpRequestParam().getMethod());

        // 2. header
        Map<String, String> header = new TreeMap<>(postTaskDto.getHttpRequestParam().getHeader());
        header.put("Content-Type", postTaskDto.getHttpRequestParam().getContentType());
        postTaskDto.setHeader(header);

        // 3. body
        if (postTaskDto.getHttpRequestParam().getContentType().toLowerCase().contains(MediaType.APPLICATION_JSON_VALUE)) {
            postTaskDto.setBody(buildJsonBody(postTaskDto).getBytes(StandardCharsets.UTF_8));
        } else if (postTaskDto.getHttpRequestParam().getContentType().toLowerCase().contains(MediaType.APPLICATION_FORM_URLENCODED_VALUE)) {
            postTaskDto.setBody(buildFormBody(postTaskDto).getBytes(StandardCharsets.UTF_8));
        } else {
            postTaskDto.setBody(postTaskDto.getHttpRequestParam().getBody().getBytes(StandardCharsets.UTF_8));
        }

        // 4.url
        postTaskDto.setUrl(postTaskDto.getHttpRequestParam().getUri());
    }

    protected String buildJsonBody(PostTaskDto postTaskDto) {
        if (Strings.isNullOrEmpty(postTaskDto.getHttpRequestParam().getBody())) {
            return "{}";
        }
        // replace .DATA
        JsonNode node = JacksonHelper.toObj(postTaskDto.getHttpRequestParam().getBody());
        replaceDataHolder(node, postTaskDto.getBizSystemParams());
        return JacksonHelper.toJson(node);
    }

    // todo: 对标HuaweiJsonRestWorker逻辑再过一次，最好仅仅做.DATA的事情，再加一个.ACTION。
    private void replaceDataHolder(JsonNode node, Map<String, Object> bizParams) {
        if (node.isValueNode()) {
            return;
        }
        if (node.isObject()) {
            Iterator<Map.Entry<String, JsonNode>> it = node.fields();
            while (it.hasNext()) {
                Map.Entry<String, JsonNode> entry = it.next();
                if (entry.getValue().isValueNode()) {
                    String paramsKey = entry.getValue().textValue().replace("{{", "").replace(".DATA}}", "");
                    String paramsValue = String.valueOf(bizParams.get(paramsKey));
                    switch (paramsValue) {
                        case "{{Timestamp.ACTION}}":
                            paramsValue = DateHelper.getISO8601Time(new Date());
                            break;
                        case "{{SignatureNonce.ACTION}}":
                            paramsValue = String.valueOf(Snowflake.get().nextId());
                            break;
                        default:
                            break;
                    }
                    entry.setValue(JacksonHelper.transferToJsonNode(paramsValue));
                } else {
                    replaceDataHolder(entry.getValue(), bizParams);
                }
            }
        }
        if (node.isArray()) {
            boolean textual = node.get(0).isTextual();
            if (!textual) {
                if (node.isValueNode()) {
                    return;
                }
                Iterator<JsonNode> elements = node.elements();
                while (elements.hasNext()) {
                    JsonNode next = elements.next();
                    if (next.isObject()) {
                        replaceDataHolder(next, bizParams);
                    }
                }
            } else {
                List<List<String>> valueLists = new ArrayList<>();
                Iterator<JsonNode> elements = node.elements();
                while (elements.hasNext()) {
                    JsonNode next = elements.next();
                    String textValue = next.textValue();
                    if (textValue.contains(".DATA")) {
                        String paramsKey = textValue.replace("{{", "").replace(".DATA}}", "");
                        String paramsValue = String.valueOf(bizParams.get(paramsKey));
                        // todo：这里的逻辑？
                        List<String> paramsValueList = Arrays.stream(paramsValue.split(",")).collect(Collectors.toList());
                        valueLists.add(paramsValueList);
                    } else {
                        valueLists.add(Collections.singletonList(textValue));
                    }
                }
                ArrayNode arrayNode = (ArrayNode) node;
                arrayNode.removeAll();
                valueLists.stream().flatMap(List::stream).forEach(arrayNode::add);
            }
        }
    }

    /**
     * 阿里云
     */
    protected String buildFormBody(PostTaskDto postTaskDto) {
        // 1.split body
        @SuppressWarnings("UnstableApiUsage")
        Map<String, String> bodyMap = new TreeMap<>(Splitter.on("&").trimResults().withKeyValueSeparator("=").split(postTaskDto.getHttpRequestParam().getBody()));

        // 2.replace .DATA & remove null value
        Iterator<Map.Entry<String, String>> it = bodyMap.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<String, String> next = it.next();
            if (!next.getValue().endsWith(".DATA}}")) {
                continue;
            }
            String paramsKey = next.getValue().replace("{{", "").replace(".DATA}}", "");
            Object paramsValue = postTaskDto.getBizSystemParams().get(paramsKey);
            if (paramsValue == null) {
                it.remove();
            } else {
                next.setValue(String.valueOf(paramsValue));
            }
        }

        // 3.replace .ACTION
        boolean signature = false;
        it = bodyMap.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<String, String> next = it.next();
            if (!next.getValue().endsWith(".ACTION}}")) {
                continue;
            }
            switch (next.getValue()) {
                case "{{Timestamp.ACTION}}":
                    next.setValue(DateHelper.getISO8601Time(new Date()));
                    break;
                case "{{SignatureNonce.ACTION}}":
                    next.setValue(String.valueOf(Snowflake.get().nextId()));
                    break;
                case "{{Signature.ACTION}}":
                    it.remove();
                    signature = true;
                    break;
                default:
                    break;
            }
        }

        // 4.signature
        String body = map2String(bodyMap, true);
        if (!signature) {
            return body;
        }
        Object accessKeySecret = postTaskDto.getBizSystemParams().get(ACCESS_KEY_SECRET.toLowerCase());
        if (accessKeySecret == null) {
            throw new RuntimeException("Not found access key secret from access params: " + ACCESS_KEY_SECRET.toLowerCase());
        }
        String sign = bodySign(postTaskDto.getHttpRequestParam().getMethod().name(), body, accessKeySecret.toString());
        return body + "&" + URLEncodeHelper.percentEncode("Signature") + "=" + URLEncodeHelper.percentEncode(sign);
    }

    public static String map2String(Map<String, String> paramsMap, boolean encode) {
        if (paramsMap.isEmpty()) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, String> entry : paramsMap.entrySet()) {
            sb.append(encode ? URLEncodeHelper.percentEncode(entry.getKey()) : entry.getKey())
                    .append("=")
                    .append(encode ? URLEncodeHelper.percentEncode(entry.getValue()) : entry.getValue())
                    .append("&");
        }
        sb.deleteCharAt(sb.length() - 1);
        return sb.toString();
    }

    protected String bodySign(String method, String body, String accessKeySecret) {
        StringBuilder sb = new StringBuilder();
        sb.append(method)
                .append("&")
                .append(URLEncodeHelper.percentEncode("/"))
                .append("&")
                .append(URLEncodeHelper.percentEncode(body));
        return HmacSHA1.genHMACBase64Encoded(sb.toString(), accessKeySecret + "&");
    }

}
